
package com.yadong.smarthome2.device;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;

import com.yadong.smarthome2.connections.Connection;
import com.yadong.smarthome2.connections.Connection.ConnectionListener;
import com.yadong.smarthome2.connections.Connection.ConnectionState;
import com.yadong.smarthome2.connections.ConnectionManager;
import com.yadong.smarthome2.connections.ResultListener;
import com.yadong.smarthome2.datastore.SmartHomeDbOperator;
import com.yadong.smarthome2.device.Device.DeviceState;
import com.yadong.smarthome2.devicefinder.DeviceFinderWorker;
import com.yadong.smarthome2.devicefinder.DeviceFinderWorker.ScanListener;
import com.yadong.smarthome2.devicefinder.DeviceListener;
import com.yadong.smarthome2.devicefinder.DeviceStatusListener;
import com.yadong.smarthome2.protocol2.ALog;
import com.yadong.smarthome2.protocol2.RequestProtocol.Request;
import com.yadong.smarthome2.protocol2.ResultProtocol.Result;

public class DeviceManagerService extends Service implements
        DeviceListener, ConnectionListener, ResultListener {
    private List<Device> mFindedDevice = new ArrayList<Device>();
    private List<Device> mPairedDevices = new ArrayList<Device>();
    private List<Device> mConnectedDevice = new ArrayList<Device>();

    private DeviceFinderWorker mFinderWorker = null;
    private List<DeviceListener> mDeviceListeners = new ArrayList<DeviceListener>();
    private List<ResultListener> mResultListeners = new ArrayList<ResultListener>();
    private ConnectionManager mConnectionManager;

    private List<DeviceStatusListener> mStatusListeners =
            new ArrayList<DeviceStatusListener>();

    private Map<Device, Request> mPostRequest = new HashMap<Device, Request>();

    private SmartHomeDbOperator mDbOperator;

    @Override
    public void onCreate() {
        super.onCreate();
        mConnectionManager = ConnectionManager.getInstance(this);
        mDbOperator = new SmartHomeDbOperator(this);
        loadPairedDevice();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent arg0) {
        return new MyBinder();
    }

    public class MyBinder extends Binder {
        public DeviceManagerService getService() {
            return DeviceManagerService.this;
        }
    }

    /**
     * add a device status listener
     * @param listener
     */
    public void addDeviceStatusListener(DeviceStatusListener listener) {
        synchronized (mStatusListeners) {
            if (mStatusListeners.indexOf(listener) == -1) {
                mStatusListeners.add(listener);
            }
        }
    }

    /**
     * remove device status listener
     * @param listener
     */
    public void removeDeviceStatusListener(DeviceStatusListener listener) {
        synchronized (mStatusListeners) {
            mStatusListeners.remove(listener);
        }
    }

    /**
     * get the device finder is working or not
     * @return
     */
    public boolean isScanningDevice() {
        return mFinderWorker != null && mFinderWorker.isWorking();
    }

    /**
     * start a device find thread.
     * @param deviceListener new device find listener
     * @param listener scan state listener
     */
    public void startFindDevice(DeviceListener deviceListener, ScanListener listener) {
        synchronized (mDeviceListeners) {
            if (mDeviceListeners.indexOf(deviceListener) == -1) {
                mDeviceListeners.add(deviceListener);
            }
        }

        notifyDeviceFinded(null);
        if (mFinderWorker == null || !mFinderWorker.isWorking()) {
            mFinderWorker = new DeviceFinderWorker(this, this, listener);
            mFinderWorker.startScan();
        }
    }

    /**
     * Add a  result listener , the result is from remote device
     * @param resultListener
     */
    public void addResultListeners(ResultListener resultListener) {
        synchronized (mResultListeners) {
            mResultListeners.add(resultListener);
        }
    }

    /**
     * remove a result listener
     * @param listener
     */
    public void removeResultListener(ResultListener listener) {
        synchronized (mResultListeners) {
            mResultListeners.remove(listener);
        }
    }

    /**
     * force stop the device finder worker
     */
    public void stopFindDevice() {
        if (mFinderWorker != null && mFinderWorker.isWorking()) {
            mFinderWorker.stopScan();
        }
        mDeviceListeners.clear();
        mFinderWorker = null;
    }

    /**
     * get all paired and finded device
     * @return
     */
    public List<Device> getFindedDevice() {
        List<Device> all = new ArrayList<Device>();
        all.addAll(mPairedDevices);
        all.addAll(mFindedDevice);
        return all;
    }

    /**
     * send data to remove device
     * @param device
     * @param message
     */
    public void sendData(Device device, Request message) {
        //check device
        if (device == null) {
            return;
        }
        //check device state
        Connection connection = mConnectionManager.getConnection(device);
        if (connection == null ||
                connection.getConnectionState() == ConnectionState.DISCONNECTED) {
            //try to connect device
            mConnectionManager.connect(device);
            //post request, when connection connected, send it
            mPostRequest.put(device, message);
        } else {
            connection.sendData(message);
        }
    }

    /**
     * try to connected device
     * @param device
     */
    public void connectDevice(Device device) {
        mConnectionManager.connect(device);
    }

    /**
     * force disconnect the deivce connection
     * @param device
     */
    public void disconnectDevice(Device device) {
        mConnectionManager.forceDisConnect(device);
    }

    /**
     * find device with device id
     * @param id
     * @return
     */
    public Device getDeviceFromId(long id) {
        for (Device device : getFindedDevice()) {
            if (device.getId() == id) {
                return device;
            }
        }
        return null;
    }

    @Override
    public void onDeviceFinded(Device findedDevice) {
        // Finded device is saved.
        if (mPairedDevices.indexOf(findedDevice) != -1) {
            Device oldDevice = mPairedDevices.get(mPairedDevices.indexOf(findedDevice));
            oldDevice.setAddress(findedDevice.getAddress());
            oldDevice.setDeviceDescription(findedDevice.getDeviceDescription());
            if (oldDevice.getId() != -1) {
                mDbOperator.updateDevice(oldDevice);
            }
        } else if (mFindedDevice.indexOf(findedDevice) == -1) {
            mFindedDevice.add(findedDevice);
        } else {
            mFindedDevice.remove(findedDevice);
            mFindedDevice.add(findedDevice);
        }
        notifyDeviceFinded(findedDevice);
    }

    @Override
    public void onConnectionDisconnected(Connection connection) {
        mConnectedDevice.remove(connection.getDevice());
        notifyDeviceStatusChanged(DeviceState.CONNECTED,
                connection.getDevice());
    }

    @Override
    public void onConnectionConnected(Connection connection) {
        // the device object is from on device finded
        Device device = connection.getDevice();

        if (mConnectedDevice.indexOf(device) == -1) {
            mConnectedDevice.add(device);
        }

        // now we will try to send request.
        Request request = mPostRequest.remove(device);
        if (request != null) {
            connection.sendData(request);
        }

        // TODO maybe check authority here
        notifyDeviceStatusChanged(DeviceState.CONNECTED,
                connection.getDevice());
    }

    @Override
    public void onConnectionFaild(Connection connection) {
        ALog.loge(DeviceManagerService.this, "on connection faild.. remove request");
        Device device = connection.getDevice();
        mPostRequest.remove(device);
    }

    @Override
    public void onDeviceInitialized(Connection connection) {
        // the device object is from on device finded
        Device device = connection.getDevice();
        // save deviceand mark as paired
        if (mFindedDevice.indexOf(device) != -1) {
            long id = (int) mDbOperator.saveDevice(connection.getDevice());
            if (id == -1) {
                ALog.loge(this, "error... to save device");
            } else {
                device.setId(id);
                markAsPaired(device);
            }
        }
        notifyDeviceStatusChanged(DeviceState.CONNECTED,
                connection.getDevice());
    }

    @Override
    public void onNewMessage(Device device, Result result) {
        notifyResult(device, result);
    }

    private void notifyDeviceStatusChanged(DeviceState state, Device device) {
        synchronized (mStatusListeners) {
            for (DeviceStatusListener listener : mStatusListeners) {
                listener.onDeviceStatusChanged(state, device);
            }
        }
    }

    private void loadPairedDevice() {
        mPairedDevices.clear();
        mPairedDevices.addAll(mDbOperator.getAllDevice());
    }

    private void notifyResult(Device device, Result result) {
        synchronized (mResultListeners) {
            for (ResultListener listener : mResultListeners) {
                listener.onNewMessage(device, result);
            }
        }
    }

    private void notifyDeviceFinded(Device device) {
        synchronized (mDeviceListeners) {
            for (DeviceListener listener : mDeviceListeners) {
                listener.onDeviceFinded(device);
            }
        }
    }

    private void markAsPaired(Device device) {
        mFindedDevice.remove(device);
        mPairedDevices.add(device);
    }

}
